package com.wngbms.fileio.file_transfer;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class BigFileReceiver {

    public static Charset charset = StandardCharsets.UTF_8;

    static class Client {
        int receivedCnt;
        int step = 1;

        String fileName;
        int fileNameLength;
        int fileLength;

        long startTs;

        InetSocketAddress remoteAddress;
        FileChannel fileChannel;

        public boolean isFinish() {
            return receivedCnt == fileLength;
        }
    }

    static Map<SelectableChannel, Client> clientMap = new HashMap<SelectableChannel, Client>();

    static ExecutorService executorService = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws IOException, InterruptedException {
        Selector selector = Selector.open();

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress("127.0.0.1", 8090));

        log.info("bind success");

        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (selector.select() > 0) {
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            if (selectionKeys == null) {
                log.info("keys is null");
                continue;
            }

            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                selectionKey = iterator.next();

                if (selectionKey.isAcceptable()) {
                    serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    if (socketChannel == null) {
                        continue;
                    }

                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
//                    SelectableChannel channel = selectionKey.channel();
//                    executorService.
                    processData(selectionKey);
                }

                iterator.remove();
            }
        }
    }


    static ByteBuffer buffer = ByteBuffer.allocateDirect(4096);

    private static void processData(SelectionKey selectionKey) throws IOException, InterruptedException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

        int read;
        while ((read = socketChannel.read(buffer)) > 0) {
            Client client = clientMap.computeIfAbsent(socketChannel, k -> new Client());
            client.remoteAddress = (InetSocketAddress) socketChannel.getRemoteAddress();

            // read mode
            buffer.flip();
            do {
                switch (client.step) {
                    case 1:
                        if (buffer.remaining() < 4) {
                            log.info("fileName length less than 4");
                            break;
                        }
                        client.fileNameLength = buffer.getInt();
                        client.step = 2;
                        log.info("fileNameLength:{}", client.fileNameLength);
                        break;
                    case 2:
                        if (buffer.remaining() < client.fileNameLength) {
                            log.info("fileName less than {}", client.fileNameLength);
                            break;
                        }

                        byte[] buf = new byte[client.fileNameLength];
                        buffer.get(buf, 0, client.fileNameLength);
                        client.fileName = new String(buf, charset);
                        client.step = 3;
                        log.info("fileName:{}", client.fileName);
                        break;
                    case 3:
                        if (buffer.remaining() < 4) {
                            log.info("file length less than 4");
                            break;
                        }

                        client.fileLength = buffer.getInt();
                        client.step = 4;
                        log.info("fileLength:{}", client.fileLength);

                        File file = new File("J:\\copy\\" + client.fileName);
                        if (!file.exists()) {
                            file.createNewFile();
                        }

                        FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();
                        client.fileChannel = fileChannel;

                        break;
                    case 4:
                        if (buffer.remaining() <= 0) {
                            log.info("buffer have no data");
                            Thread.sleep(1000);
                            break;
                        }

                        int received;
                        int min = Math.min(buffer.remaining(), client.fileLength - client.receivedCnt);
                        received = (int) client.fileChannel.transferFrom(socketChannel, client.receivedCnt, min);
                        log.info("received:{} total:{}", received, client.fileLength);
                        client.receivedCnt += received;

                        if (client.isFinish()) {
                            clientMap.remove(socketChannel);
                            log.info("文件{}接收完成", client.fileName);
                            break;
                        }
                        break;
                    default:
                        log.info("error");
                }
            } while (buffer.remaining() > 0);

            // write mode
            buffer.compact();
        }
    }

}
